/*
 * Copyright 2017 StreamSets Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.el;

import com.streamsets.datacollector.el.ELEvaluator;
import jakarta.el.ExpressionFactory;
import jakarta.el.ValueExpression;
import jakarta.servlet.jsp.el.Expression;
import jakarta.servlet.jsp.el.*;
import jakarta.el.FunctionMapper;
import org.apache.commons.collections4.map.LRUMap;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * StreamSets specific subclass of ExpressionEvaluatorImpl that workarounds
 * memory leak in the implementation that is tracked by EL-1. This class will
 * be removed once EL-1 fix is available.
 */
@SuppressWarnings("unchecked")
public class LruExpressionEvaluatorImpl extends ExpressionEvaluatorImpl {
  private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LruExpressionEvaluatorImpl.class);

  /**
   * Max LRU size, right now it's a magic constant.
   */
  private static final int MAX_LRU_SIZE = 5000;

  private  static Map sCachedExpressionStrings = Collections.synchronizedMap(new HashMap());
  private  static Map sCachedExpectedTypes = new HashMap();

  private final  static ExpressionFactory factory;

   public LruExpressionEvaluatorImpl(){
       super(factory);
   }
  /**
   * Change the static caches to use LRU map rather than normal map.
   */
  static {
    sCachedExpressionStrings = Collections.synchronizedMap(new LRUMap(MAX_LRU_SIZE));
    sCachedExpectedTypes = new LRUMap(MAX_LRU_SIZE);
    factory = ExpressionFactory.newInstance();
    Thread thread = new Thread("EL-cache-trimmer") {
      @Override
      public void run() {
        LOG.info(" Starting housekeeper thread for expected types cache (runs every 5mins)");
        while (true) {
          try {
            LOG.debug("Clearing expected types cache");
            sleep(5 * 60 * 1000);
            synchronized (sCachedExpectedTypes) {
              sCachedExpectedTypes.clear();
            }
          } catch (InterruptedException ex) {
          }
        }
      }
    };
    thread.setDaemon(true);
    thread.start();
  }

//   @Override
//    public Expression parseExpression(String expression,
//            @SuppressWarnings("rawtypes") Class expectedType,
//            FunctionMapper fMapper) throws ELException {
//        try {
//            ELContextImpl ctx =
//                new ELContextImpl(ELContextImpl.getDefaultResolver(factory));
//            if (fMapper != null) {
////                ctx.setFunctionMapper((new ELEvaluator()).FunctionMapperImpl(fMapper));
//              ctx.setFunctionMapper(fMapper);
//            }
//            ValueExpression ve = factory.createValueExpression(ctx, expression, expectedType);
//            return new ExpressionImpl(ve, factory);
//        } catch (ELException e) {
//            throw new ELParseException(e.getMessage());
//        }
//    }
//
//    @Override
//    public Object evaluate(String expression,
//                           @SuppressWarnings("rawtypes") Class expectedType,
//                           VariableResolver vResolver, FunctionMapper fMapper)
//            throws ELException {
//        return this.parseExpression(expression, expectedType, fMapper).evaluate(vResolver);
//    }
}
